{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 施磊: 从指令角度掌握函数调用堆栈详细过程"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# `main` 调用 `sum` 的堆栈分析"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "int sum(int a, int b) {\n",
    "    int temp = a + b;\n",
    "    return temp;\n",
    "}\n",
    "\n",
    "int main() {\n",
    "    int a = 10, b = 20, ret;\n",
    "    ret = sum(a, b);\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "plaintext"
    }
   },
   "outputs": [],
   "source": [
    "; -m64\n",
    "sum(int, int):\n",
    "        push    rbp                    ; 保存调用者的基址指针 rbp，压栈\n",
    "        mov     rbp, rsp               ; 建立当前函数的栈帧，rbp 指向新的栈底（高地址）\n",
    "        mov     DWORD PTR [rbp-20], edi; 把第 1 个参数 (a) 备份到栈帧中偏移 -20 的位置\n",
    "        mov     DWORD PTR [rbp-24], esi; 把第 2 个参数 (b) 备份到栈帧中偏移 -24 的位置\n",
    "        mov     edx, DWORD PTR [rbp-20]; 读取参数 a 到寄存器 edx\n",
    "        mov     eax, DWORD PTR [rbp-24]; 读取参数 b 到寄存器 eax\n",
    "        add     eax, edx               ; eax = eax + edx = b + a\n",
    "        mov     DWORD PTR [rbp-4], eax ; 结果暂存到局部变量 temp（位于 rbp-4）\n",
    "        mov     eax, DWORD PTR [rbp-4] ; 将 temp 的值装回 eax，作为返回值\n",
    "        pop     rbp                    ; 恢复调用者的 rbp（弹栈）\n",
    "        ret                            ; 从栈顶弹出返回地址，跳回调用点\n",
    "\n",
    "main:\n",
    "        push    rbp                    ; 保存调用者（C 运行时）的 rbp\n",
    "        mov     rbp, rsp               ; 建立 main 的栈帧\n",
    "        sub     rsp, 16                ; 为局部变量预留 16 字节栈空间（对齐 + a/b/ret 等）\n",
    "        mov     DWORD PTR [rbp-4], 10  ; 将常量 10 写入局部变量 a（rbp-4）\n",
    "        mov     DWORD PTR [rbp-8], 20  ; 将常量 20 写入局部变量 b（rbp-8）\n",
    "        mov     edx, DWORD PTR [rbp-8] ; 将 b 的值载入 edx\n",
    "        mov     eax, DWORD PTR [rbp-4] ; 将 a 的值载入 eax\n",
    "        mov     esi, edx               ; System V AMD64 约定：第 2 个 int 参数放入 esi\n",
    "        mov     edi, eax               ; 第 1 个 int 参数放入 edi\n",
    "        call    sum(int, int)          ; 调用 sum，返回地址压栈，参数已在寄存器中\n",
    "        mov     DWORD PTR [rbp-12], eax; 把 sum 返回的结果 (eax) 保存到局部变量 ret（rbp-12）\n",
    "        mov     eax, 0                 ; main 返回值设为 0\n",
    "        leave                          ; 等价于 mov rsp, rbp -> pop rbp，撤销栈帧\n",
    "        ret                            ; 返回到 C 运行时入口"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "plaintext"
    }
   },
   "outputs": [],
   "source": [
    "; https://godbolt.org/z/x7xffWdWY -m32\n",
    "\n",
    "sum(int, int):\n",
    "        push    ebp                    ; 保存调用者的基址寄存器，压栈\n",
    "        mov     ebp, esp               ; 建立当前函数的栈帧：ebp 指向原来的栈顶\n",
    "        sub     esp, 16                ; 预留 16 字节局部变量空间（对齐 + temp 等）\n",
    "        mov     edx, DWORD PTR [ebp+8] ; 取第一个参数 a（位于 ebp+8）到 edx\n",
    "        mov     eax, DWORD PTR [ebp+12]; 取第二个参数 b（位于 ebp+12）到 eax\n",
    "        add     eax, edx               ; eax = b + a\n",
    "        mov     DWORD PTR [ebp-4], eax ; 把运算结果暂存到 temp（栈帧内偏移 -4）\n",
    "        mov     eax, DWORD PTR [ebp-4] ; 将 temp 的值装回 eax，作为返回值\n",
    "        leave                          ; 等价于 mov esp, ebp → pop ebp，撤销栈帧\n",
    "        ret                            ; 从栈顶弹出返回地址，跳回调用点\n",
    "\n",
    "main:\n",
    "        push    ebp                    ; 保存上层（运行时）的 ebp\n",
    "        mov     ebp, esp               ; 建立 main 的栈帧\n",
    "        sub     esp, 16                ; 给局部变量预留 16 字节空间（对齐）\n",
    "        mov     DWORD PTR [ebp-4], 10  ; a = 10，写入栈帧偏移 -4\n",
    "        mov     DWORD PTR [ebp-8], 20  ; b = 20，写入栈帧偏移 -8\n",
    "        push    DWORD PTR [ebp-8]      ; 按右到左顺序压入参数 b\n",
    "        push    DWORD PTR [ebp-4]      ; 压入参数 a\n",
    "        call    sum(int, int)          ; 调用 sum：压入返回地址并跳转\n",
    "        add     esp, 8                 ; 调用者清理参数区，两次 push 共 8 字节\n",
    "        mov     DWORD PTR [ebp-12], eax; 保存返回值到 ret（栈帧偏移 -12）\n",
    "        mov     eax, 0                 ; main 的返回值设为 0\n",
    "        leave                          ; 撤销栈帧：mov esp, ebp → pop ebp\n",
    "        ret                            ; 返回到 C 运行时入口"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**调用约定**：此汇编遵循 System V AMD64（Linux/GCC 常见），`int` 形参依次放入 `edi`、`esi`、`edx`、`ecx`、`r8d`、`r9d`。因此 main 在调用前把 `a` 的值放入 `edi`，`b` 的值放入 `esi`"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`main` 调用 `sum`，`sum` 执行完以后，怎么知道回到哪个函数中？\n",
    "\n",
    "1. 在执行 `call sum` 指令时，CPU 会自动完成两件事：\n",
    "- **把下一条指令的地址压入栈中**，这个地址就是“返回地址”；\n",
    "- **跳转到 `sum` 函数的入口**开始执行。\n",
    "2. 因此，当 `sum` 结束时，调用栈顶保存的就是“应该回到哪里”的信息。无论是谁调用 `sum`，`call` 都会把调用者的返回地址压栈，`sum` 只需按约定执行 `ret`，就能准确回到发起调用的那个函数（此处是 `main`）。\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`sum` 执行完，回到 `main` 以后，怎么知道从哪一行指令继续运行？\n",
    "\n",
    "1. `sum` 的结尾会执行标准的函数尾声：`mov esp, ebp` → `pop ebp` → `ret`。\n",
    "2. `ret` 指令会：\n",
    "- **从栈顶弹出刚才 `call` 压入的返回地址**；\n",
    "- **把这个地址写入指令指针寄存器（如 x86 平台的 `EIP`）**；\n",
    "- CPU 读取 `EIP` 的值，就会从被弹出的那条指令继续执行。\n",
    "3. 对于 `main` 来说，这个返回地址指向的是 `call sum` 的下一条指令，也就是编译器为 `ret` 赋值或输出结果的那句代码。因此 `main` 可以无缝地从调用点后面的指令继续往下运行。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 栈帧构造与内存布局"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**栈帧创建**\n",
    "\n",
    "`ESP` 指向栈顶（低地址），`EBP` 指向当前栈底（高地址）\n",
    "\n",
    "```assembly\n",
    "main:\n",
    "        push    ebp                    ; 保存调用者的基址\n",
    "        mov     ebp, esp               ; 当前函数的 rbp/rsp 建立新的栈帧\n",
    "```\n",
    "\n",
    "**栈内存自高地址向低地址增长**\n",
    "\n",
    "```aseembly\n",
    "main:\n",
    "        push    ebp                    ; 保存调用者的基址\n",
    "        mov     ebp, esp               ; 当前函数的 rbp/rsp 建立新的栈帧\n",
    "        sub     esp, 16                ; 让栈顶指针向低地址移动，为局部变量留空间\n",
    "```\n",
    "\n",
    "` sub esp, 16`: 在大多数 CPU 架构（包括 x86/x86-64）上，栈的生长方向是从高地址向低地址扩展的. 栈顶寄存器数值变小 → 指向的地址更低 → 栈空间增大。\n",
    "- 取当前 `ESP` 的值（栈顶地址）。\n",
    "- 减去 16，把 `ESP` 更新为“旧地址减 16”的结果。\n",
    "- 由于内存地址是线性递增的，从高到低移动意味着栈向“下”扩展。\n",
    "- 新的 `ESP` 指向更低的地址，代表我们预留了 16 字节的栈空间（通常用于局部变量或保存临时数据）。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "int sum(int a, int b) {\n",
    "    int temp = a + b;\n",
    "    return temp;\n",
    "}\n",
    "\n",
    "int main() {\n",
    "    int a = 10, b = 20, ret;\n",
    "    ret = sum(a, b);\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**局部变量的偏移访问**\n",
    "\n",
    "- 这里偏移量分别是 -20/-24/-4，因为编译器要满足 16 字节对齐要求\n",
    "```assembly\n",
    "; 在 sum 中的三条指令\n",
    "mov     edx, DWORD PTR [ebp+8] ; 备份第一个参数\n",
    "mov     eax, DWORD PTR [ebp+12]; 备份第二个参数\n",
    "add     eax, edx               ; eax = b + a\n",
    "mov     DWORD PTR [ebp-4], eax ; 存放临时变量 temp\n",
    "```\n",
    "\n",
    "- 局部变量按声明顺序存放在 [ebp-4]、[ebp-8]、[ebp-12]\n",
    "```assembly\n",
    "; 在 main 中\n",
    "mov DWORD PTR [ebp-4], 10    ; a = 10\n",
    "mov DWORD PTR [ebp-8], 20    ; b = 20\n",
    "mov DWORD PTR [ebp-12], eax  ; ret = sum(...)\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "cpp"
    }
   },
   "outputs": [],
   "source": [
    "int f()\n",
    "{\n",
    "    int a = 10;\n",
    "    int b = 20;\n",
    "    int sum = a + b;\n",
    "\n",
    "    int uninit; // ❗ 未初始化的局部变量\n",
    "    return sum;\n",
    "}\n",
    "\n",
    "int main()\n",
    "{\n",
    "    f();\n",
    "    return 0;\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "plaintext"
    }
   },
   "outputs": [],
   "source": [
    "; https://godbolt.org/z/5xhvfMj4G -std=c++20 -m32 -g -O0\n",
    "\n",
    "f():\n",
    "    push    ebp                     ; 保存调用者的基址寄存器\n",
    "    mov     ebp, esp                ; 建立当前函数的栈帧\n",
    "    sub     esp, 16                 ; 给局部变量预留 16 字节（对齐 + 防优化项）\n",
    "    mov     DWORD PTR [ebp-4], 10   ; a = 10    → 栈偏移 -4\n",
    "    mov     DWORD PTR [ebp-8], 20   ; b = 20    → 栈偏移 -8\n",
    "    mov     edx, DWORD PTR [ebp-4]  ; edx = a\n",
    "    mov     eax, DWORD PTR [ebp-8]  ; eax = b\n",
    "    add     eax, edx                ; eax = a + b\n",
    "    mov     DWORD PTR [ebp-12], eax ; sum = a + b → 栈偏移 -12\n",
    "    ; ⚠️ 注意：没有针对 [esp-16]（uninit）的写操作\n",
    "    mov     eax, DWORD PTR [ebp-12] ; 返回值：把 sum 放回 eax\n",
    "    leave                           ; 恢复栈帧（mov esp, ebp ➜ pop ebp）\n",
    "    ret                             ; 返回\n",
    "main:\n",
    "    push    ebp                     ; 保存调用者基址\n",
    "    mov     ebp, esp                ; 建栈帧（这里没有局部变量）\n",
    "    call    f                       ; 调用 f，返回值在 eax\n",
    "    mov     eax, 0                  ; main 返回 0\n",
    "    pop     ebp                     ; 恢复调用者的 ebp\n",
    "    ret                             ; 返回给运行时"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "plaintext"
    }
   },
   "outputs": [],
   "source": [
    "// https://godbolt.org/z/dPecrGdET MSVC x32\n",
    "\n",
    "# License: MSVC Proprietary\n",
    "# The use of this compiler is only permitted for internal evaluation purposes and is otherwise governed by the MSVC License Agreement.\n",
    "# See https://visualstudio.microsoft.com/license-terms/vs2022-ga-community/\n",
    "_sum$ = -12 ; size = 4\n",
    "_b$ = -8 ; size = 4\n",
    "_a$ = -4 ; size = 4\n",
    "int f(void) PROC         ; 函数入口标签\n",
    "\n",
    "  push ebp               ; 保存上一层的栈帧基址\n",
    "  mov  ebp, esp          ; 建立当前函数的栈帧\n",
    "  sub  esp, 12           ; 预留 12 字节局部变量（3 个 int，各 4 字节）\n",
    "\n",
    "  mov DWORD PTR _a$[ebp], 10   ; a = 10    → [ebp-4]\n",
    "  mov DWORD PTR _b$[ebp], 20   ; b = 20    → [ebp-8]\n",
    "\n",
    "  mov eax, DWORD PTR _a$[ebp]  ; eax = a\n",
    "  add eax, DWORD PTR _b$[ebp]  ; eax += b\n",
    "  mov DWORD PTR _sum$[ebp], eax; sum = a + b → [ebp-12]\n",
    "\n",
    "  mov eax, DWORD PTR _sum$[ebp]; 返回值放进 eax（ABI 约定）\n",
    "\n",
    "  mov esp, ebp           ; 等价 leave：撤销局部变量\n",
    "  pop ebp\n",
    "  ret 0                  ; 返回，告诉调用者调用约定中的参数长度（此处 0）\n",
    "\n",
    "int f(void) ENDP\n",
    "\n",
    "_main PROC\n",
    "  push ebp\n",
    "  mov  ebp, esp\n",
    "  call int f(void)       ; 调用 f，返回值在 eax\n",
    "  xor  eax, eax          ; main 返回 0\n",
    "  pop  ebp\n",
    "  ret 0                  ; 同样是 cdecl：main 无参数\n",
    "_main ENDP"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**未初始化可能是垃圾值**\n",
    "\n",
    "如果有局部变量未初始化，编译器就不会对它进行 mov 赋值，你会看到对应的内存槽（例如 [ebp-12]）在函数执行前没有被写入——这就是所谓“默认值不确定”。在调试模式下，某些编译器会额外插入 mov 或 memset 填充为 0xCC/0xCD (GCC , MSVC没出现这种情况)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "C++17",
   "language": "C++17",
   "name": "xcpp17"
  },
  "language_info": {
   "codemirror_mode": "text/x-c++src",
   "file_extension": ".cpp",
   "mimetype": "text/x-c++src",
   "name": "C++17",
   "version": "17"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
